<template><div><h1 id="java基础篇" tabindex="-1"><a class="header-anchor" href="#java基础篇"><span>Java基础篇</span></a></h1>
<h2 id="equals与-的区别" tabindex="-1"><a class="header-anchor" href="#equals与-的区别"><span>equals与==的区别</span></a></h2>
<p>在Java中，<code v-pre>==</code>是一个比较操作符，用于比较两个变量的值是否相等。 <code v-pre>equals()</code>是Object类中定义的方法，用于比较两个对象是否相等。
<strong>具体区别如下：</strong>
<code v-pre>==</code>用于比较基本数据类型和引用类型的变量地址值是否相同。对于基本数据类型，比较的是它们的实际值；对于引用类型，比较的是它们所引用的对象的地址值。
<code v-pre>equals()</code>方法用于比较两个对象的内容是否相等。默认情况下，它与<code v-pre>==</code>的作用相同，比较的是对象的地址值。但是，可以根据具体的类重写该方法，以实现自定义的比较逻辑。</p>
<h2 id="final、finally、finalize的区别" tabindex="-1"><a class="header-anchor" href="#final、finally、finalize的区别"><span>final、finally、finalize的区别</span></a></h2>
<ol>
<li><strong>final</strong></li>
</ol>
<ul>
<li>final是一个修饰符，可以用于修饰类、方法和变量。
<ul>
<li>用于修饰类时，表示该类不能被继承、即为最终类。</li>
<li>用于修饰方法时，表示该方法不能被子类重写。</li>
<li>用于修饰变量时，表示该变量是一个常量，其值不能被修改。</li>
</ul>
</li>
</ul>
<ol start="2">
<li><strong>finally</strong></li>
</ol>
<ul>
<li>finally是一个关键字，用于定义一个代码块，通常与try-catch结构一起使用。</li>
<li>finally块中的代码无论是否抛出异常，都会被执行。</li>
<li>finally块通常用于释放资源、关闭连接或执行必要的清理操作。</li>
</ul>
<ol start="3">
<li><strong>finalize</strong></li>
</ol>
<ul>
<li>finalize是Object类中的一个方法，被用于垃圾回收机制。</li>
<li>finalize方法在对象被垃圾回收之前被调用，用于进行资源释放或者其他清理操作。</li>
<li>通常情况下，我们不需要显式地调用finalize方法，而是交由垃圾回收器自动调用。</li>
<li>请注意：finalize方法已被废弃，不推荐使用。在现在的Java中，可以使用<code v-pre>try-with-resources</code>语句或手动释放资源的方式来替代finalize方法的功能。</li>
</ul>
<h2 id="重载和重写的区别" tabindex="-1"><a class="header-anchor" href="#重载和重写的区别"><span>重载和重写的区别</span></a></h2>
<ol>
<li><strong>重载</strong></li>
</ol>
<ul>
<li>重载是指在同一个类中，可以有多个方法名相同但参数类型、参数个数或参数顺序不同的方法。</li>
<li>重载方法的返回值可以相同也可以不同，但不足以区别重载方法。</li>
<li>重载的作用是增加方法的灵活行和可读性，让同一个方法名可以对不同情况进行处理。</li>
</ul>
<ol start="2">
<li><strong>重写</strong></li>
</ol>
<ul>
<li>重写是指在子类中，可以对父类的方法进行重写，即对父类的方法名、返回值类型、参数列表和访问修饰符等进行重新定义。</li>
<li>重写方法必须与被重写方法拥有相同的方法名、返回值类型和参数列表，但是可以更改访问修饰符、抛出的异常类型和方法体等。</li>
<li>重写的作用是实现多态性，通过父类引用调用子类对象的方法，实现对同一方法名的不同实现。</li>
</ul>
<p><strong>区别：</strong></p>
<ul>
<li>重载是指在同一个类中对相同方法名的多次定义，而重写是指在继承关系中对父类方法的重新定义。</li>
<li>重载的方法定义签名（方法名、参数类型、个数和顺序）必须不同，而重写的方法签名是必须相同。</li>
<li>重载的目的是提供更加灵活的方法调用方式，重写的目的是熟悉多态性。</li>
</ul>
<h2 id="两个对象的hashcode值相同-则equals-是否也一定为true" tabindex="-1"><a class="header-anchor" href="#两个对象的hashcode值相同-则equals-是否也一定为true"><span>两个对象的hashCode值相同，则equals()是否也一定为true？</span></a></h2>
<p><strong>不一定。</strong>
根据Java的规范，如果两个对象的hashCode()返回值相同，那么它们可能相等，但并不保证一定相等。在某些情况下，两个不同的对象可能会产生相同的哈希码，这就是所谓的<strong>哈希冲突</strong>。因此，在判断两个对象是否相等时，还需要使用equals()方法进一步比较。</p>
<p>如果两个对象通过equals()方法比较后相等，那么它们的hashcode必须相等。</p>
<p>所以为了确保正确的相等性判断，通常需要同时重写equals()和hashCode()方法。在重写equals()方法时，需要定义满足等价关系的比较规则，包括自反性、对成性、传递性和一致性。同时，重写hashCode()方法时，需要保证如果两个对象相等，则它们的哈希码也必须相等，以避免哈希冲突。</p>
<p><strong>总结：</strong> 两个对象的hashCode()方法返回相同的值，并不能保证它们的equals()方法一定返回true，因此在比较对象的相等性时，需要同时使用equals()方法和hashCode()方法。</p>
<h2 id="抽象类和接口的区别" tabindex="-1"><a class="header-anchor" href="#抽象类和接口的区别"><span>抽象类和接口的区别</span></a></h2>
<ul>
<li><strong>定义和设计：</strong> 抽象类使用abstract关键字定义的类，可以包含抽象方法和非抽象方法，可以有实例变量和构造方法；接口通过interface关键字定义，只能包含抽奖方法、默认方法和静态方法，不包含实例变量或构造方法。</li>
<li><strong>继承关系:</strong> 在Java中，一个类只能继承自一个抽象类，但可以实现多个接口。继承抽奖类体现的是&quot;is-a&quot;关系，而实现接口体现的是&quot;can-do&quot;关系。</li>
<li><strong>构造方法：</strong> 抽象类可以有构造方法，子类可以通过super()调用父类的构造方法；接口没有构造方法。</li>
<li><strong>默认实现：</strong> 抽象类可以包含非抽象方法，子类可以直接使用；接口可以包含默认方法，提供通用实现，子类可以选择重写或者使用默认实现。</li>
<li><strong>设计目的：</strong> 抽象类的设计目的是提供类的继承机制，实现代码复用，适用于拥有相似行为和属性的类；接口的设计目的是定义一组规范或契约，实现类遵循特定的行为和功能，适用于不同类之间的解耦和多态性实现。</li>
</ul>
<h2 id="string、stringbuffer、stringbuilder的区别" tabindex="-1"><a class="header-anchor" href="#string、stringbuffer、stringbuilder的区别"><span>String、StringBuffer、StringBuilder的区别</span></a></h2>
<ol>
<li><strong>可变性</strong>
<ul>
<li>String是不可变的类，一旦创建就不能被修改。每次对String进行操作时，都会创建一个新的String对象。</li>
<li>StringBuffer和StringBUilder是可变的类，可以动态修改字符串内容。</li>
</ul>
</li>
<li><strong>线程安全</strong>
<ul>
<li>String是线程安全的，因为它是不可变的。多个线程可以同时访问同一个String对象而无需担心数据的修改问题。</li>
<li>StringBuffer是线程安全的，它的方法使用了synchronized关键字进行同步，保证在多线程环境下的安全性。</li>
<li>StringBuilder是非线程安全的，它的方法没有通过synchronized关键字保证同步，所以在多线程环境下使用时，需要手动进行同步控制。</li>
</ul>
</li>
<li><strong>性能</strong>
<ul>
<li>由于String是不可变的，每次对String进行操作都会创建一个新的String对象，频繁的字符串拼接会导致大量的对象创建和内存消耗。</li>
<li>StringBuffer是可变的，对字符串的修改是在原有对象上进行，不会创建新的对象，因此在频繁的字符串拼接场景下比String更高效。</li>
<li>StringBuilder与StringBuffer类似，由于没有通过synchronized进行同步，因此在单线程环境下性能更高。</li>
</ul>
</li>
</ol>
<p><strong>总结：</strong> 如果在单线程环境下进行字符串操作，且不需要频繁修改字符串，推荐使用String；如果在多线程环境下进行字符串操作，或者需要频繁修改字符串，优先考虑使用StringBuffer；如果在单线程环境下进行频繁的字符串拼接或修改该，推荐使用StringBuilder以获得更好的性能。</p>
<h2 id="bio、nio、aio有什么区别" tabindex="-1"><a class="header-anchor" href="#bio、nio、aio有什么区别"><span>BIO、NIO、AIO有什么区别</span></a></h2>
<ol>
<li><strong>阻塞与非阻塞：</strong>
<ul>
<li>BIO是阻塞型I/O模型，线程会一直被阻塞等待操作完成。</li>
<li>NIO是非阻塞型I/O模型，线程可以去做其他任务，当I/O操作完成时得到通知。</li>
<li>AIO也是非阻塞型I/O模型，不需要用户线程关注I/O事件，由操作系统通过回调机制处理。</li>
</ul>
</li>
<li><strong>缓冲区：</strong>
<ul>
<li>BIO使用传统的字节流和字符流，需要为输入输出流分别创建缓冲区。</li>
<li>NIO引入了基于通道和缓冲区的I/O方式，使用一个缓冲区完成数据读写操作。</li>
<li>AIO则不需要缓冲区，使用异步回调的方式进行操作。</li>
</ul>
</li>
<li><strong>线程模型：</strong>
<ul>
<li>BIO采用一个线程处理一个请求的方式，面对高并发时线程数量急剧增加，容易导致系统崩溃。</li>
<li>NIO采用多路复用器来监听多个客户端请求，使用一个线程处理，减少线程数量，提高系统性能。</li>
<li>AIO依靠操作系统完成I/O操作，不需要额外的线程池或多路复用器。</li>
</ul>
</li>
</ol>
<h2 id="java中的基本数据类型有哪些" tabindex="-1"><a class="header-anchor" href="#java中的基本数据类型有哪些"><span>Java中的基本数据类型有哪些</span></a></h2>
<p><strong>8种数据类型</strong></p>
<ol>
<li><strong>整数类型（4种）：</strong> byte、short、int、long</li>
<li>*<strong>浮点型（2种）：</strong> float、double</li>
<li><strong>字符类型（1种）：</strong> char</li>
<li><strong>布尔类型（1种）：</strong> boolean</li>
</ol>
<h2 id="cpmparator与comparable有什么区别" tabindex="-1"><a class="header-anchor" href="#cpmparator与comparable有什么区别"><span>Cpmparator与Comparable有什么区别</span></a></h2>
<p><strong>Comparator接口是一个独立的比较器</strong>，它可以用于对不同类的对象进行排序。Comparator接口允许在对象类之外创建一个单独的比较器类或匿名类，并使用它来定义对象的排序规则。比较器通过实现compare方法来比较两个对象的大小关系。
<strong>Comparable接口是在自身的类中实现的</strong>，它定义了对象的自然排序方式。一个类实现了Comparable接口后，可以使用compareTo方法来比较当前对象和其他对象的大小关系。这个接口只在对象自身的类中实现，不需要额外的比较器。</p>
<p>因此，主要的区别如下：</p>
<ul>
<li>Comparable接口是在自身的类中实现，定义了对象的自然排序方法。</li>
<li>Comparator接口是一个单独的比较器，定义了用于排序的规则，可以用于不同类的对象排序。</li>
<li>Comparable是内部排序，对象的类必须实现Comparable接口才能进行排序。</li>
<li>Comparator是外部排序，可以独立定义排序规则，并于任何类的对象一起使用。</li>
</ul>
<p>在使用时，如果需要对对象的默认排序进行操作时，可以实现Comparable接口。如果需要对不同类的对象进行排序时，或许需要定义多种不同的排序规则，可以使用Comparator接口。</p>
<h2 id="string类可以被继承吗-为什么" tabindex="-1"><a class="header-anchor" href="#string类可以被继承吗-为什么"><span>String类可以被继承吗？为什么</span></a></h2>
<p><strong>不可以</strong>
在Java中，String类是被final关键字修饰的，即不可继承。因为String类具有不可变性和安全性，这些特性可以防止一些潜在的问题，如字符串池中的重用和安全性漏洞。</p>
<p>如果String类能被继承，子类有可能修改原字符串的值，这将破坏字符串对象的不可变性。此外，String类的方法和变量都被设计成private、final和static的，这说明它们不能被重写或隐藏。如果String类可以被继承，这些设计决策将被打破，可能产生更多的问题。</p>
<h2 id="java中变量和常量有什么区别" tabindex="-1"><a class="header-anchor" href="#java中变量和常量有什么区别"><span>Java中变量和常量有什么区别</span></a></h2>
<ol>
<li><strong>可变性：</strong>
<ul>
<li>变量是可以被修改的，其值可以在程序的执行过程中改变。</li>
<li>常量是不可被修改的，其值在定义后不能再被改变。</li>
</ul>
</li>
<li><strong>声明与赋值：</strong>
<ul>
<li>变量需要先声明，并可以在声明后进行赋值。声明时需要执行变量的类型。</li>
<li>常量在定义时需要使用final关键字进行修饰。</li>
</ul>
</li>
<li><strong>内存空间：</strong>
<ul>
<li>变量在内存中占用一块存储空间，可以改变这个存储空间中的值。</li>
<li>常量通常会被编译器在编译时直接替换为对应的值，所以在内存中不会为常量分配额外的存储空间，而是直接使用常量的值。</li>
</ul>
</li>
<li><strong>使用场景：</strong>
<ul>
<li>变量用于存储会发生变化的数据，例如计数器、临时结果等，在程序的执行过程汇总可以根据需要改变其值。</li>
<li>常量用于不可变的数据，例如数学常数、配置项等，在程序中通常希望保持其固定的值，避免无操作导致值的变化。</li>
</ul>
</li>
</ol>
<h2 id="int和integer的区别" tabindex="-1"><a class="header-anchor" href="#int和integer的区别"><span>int和Integer的区别</span></a></h2>
<ol>
<li><strong>数据类型：</strong> int是Java的基本数据类型，而Integer是int的包装类，属于引用类型。</li>
<li><strong>可空性：</strong> int是基本数据类型，它不能为null。而Integer是一个对象，可以为null。</li>
<li><strong>自动装箱与拆箱：</strong> int可以直接赋值给Integer，这个过程称为自动装箱；而Integer也可以直接赋值给int，这个过程称为自动拆箱。</li>
<li><strong>性能和内存开销：</strong> 由于int是基本数据类型，它的值直接存储在栈内存中，占用的空间较小且访问速度快；而Integer是对象，它的值存储在堆内存中，占用的空间相对较大，并且访问速度较慢。因此，频繁使用的整数推荐使用int，不需要使用对象特性时可以避免使用Integer。</li>
<li><strong>默认值：</strong> int的默认值是0，Integer的默认值是null。</li>
</ol>
<h2 id="说说你对integer缓存的理解" tabindex="-1"><a class="header-anchor" href="#说说你对integer缓存的理解"><span>说说你对Integer缓存的理解</span></a></h2>
<p>在Java中，Integer类对于一定范围的整数值进行了缓存。该范围值默认是从-128到127。这意味着当创建一个Integer对象并赋值在此范围内的整数时，会直接从缓存中返回该数字对应的Integer对象，而不会每次都创建新的对象。
这种缓存的设计（<code v-pre>享元模式</code>）主要处于性能和内存优化的考虑。由于整数在编程中经常被使用，通过缓存重用Integer对象可以减少频繁的创建和销毁对象所带来的开销，同时节省了内存空间。因为缓存中的对象是提前创建好的，，所以可以直接复用，不需要每次创建新的对象。
当超过-128~127的整数仍然会创建新的Integer对象。因此，在使用<code v-pre>==</code>比较Integer对象时，推荐使用<code v-pre>equals()</code>方法进行值的比较，以避免因为缓存机制而产生的意外结果。</p>
<h2 id="java中的异常处理机制是怎么样的" tabindex="-1"><a class="header-anchor" href="#java中的异常处理机制是怎么样的"><span>Java中的异常处理机制是怎么样的</span></a></h2>
<p>异常是在程序执行过程中可能出现的错误或意外情况。他们通常表示了在程序无法正常处理的情况，如除零错误、空指针引用、文件不存在等。</p>
<p>Java中的异常处理通过使用try-catch-finally语句块来捕获和处理异常。
具体的处理过程如下:</p>
<ol>
<li>使用try块包裹可能会抛出异常的代码块，一旦在try块中发生了异常，程序的控制流会立即跳转到与之对应的catch块。</li>
<li>在catch块中，可以指定捕获特定类型的异常，并提供相应的处理逻辑。如果发生了指定类型的异常，程序会跳转到相应catch块进行处理。一个try块可以有多个catch块，分别处理不同类型的异常。</li>
<li>如果某个catch块成功处理了异常，程序将继续执行catch块之后的代码。</li>
<li>在catch块中，可以通过throw语句重新抛出异常，将异常交给上一级的调用者处理。</li>
<li>可以使用finally块来定义无论是否发生异常都需要执行的代码。finally块中的代码始终会被执行，无论异常是否被捕获。</li>
</ol>
<p>通过合理使用异常处理机制，可以使程序更具健壮性和容错性。在处理异常时，应根据具体情况选择时恢复正常执行、报告错误给用户，还是终止程序运行。同时，应避免过度捕获异常和不处理异常导致的问题，以及使用异常替代正常程序流程控制的做法。</p>
<h2 id="说说反射用途及实现原理" tabindex="-1"><a class="header-anchor" href="#说说反射用途及实现原理"><span>说说反射用途及实现原理</span></a></h2>
<p><strong>反射，允许程序在运行时动态地获取和操作类的信息。</strong> 通过反射，我们可以在编译时未知的情况下，获取类的构造函数、方法、字段，并在运行时动态创建对象、调用方法及访问和修改字段的值。</p>
<p>首先，反射提供了一种动态加载类的机制，是得我们可以在运行时根据需要加载外部的类和资源，实现插件化的构架。</p>
<p>其次，反射能够实现对象的动态创建和初始化。通过获取类的构造函数，并调用<code v-pre>newInstance()</code>方法，我们可以在运行时动态地创建对象，而不需要提前知道具体的类名。</p>
<p>另外，通过反射可以动态地调用类的方法。可以获取类的方法对象，并使用<code v-pre>invoke()</code>方法来调用这些方法，甚至可以调用私有方法。</p>
<p>反射还允许我们获取类的字段信息，并在运行时对其进行读取和修改该。通过获取字段对象并使用<code v-pre>get()</code> 和 <code v-pre>set()</code>方法，我们可以访问和修改类的字段，包括私有字段。</p>
<p>此外，反射还提供了检查类的注解、泛型信息以及父类和接口的能力，为框架开发和工具编写提供了便利。</p>
<p>尽管反射提供了很大的灵活性，但也需要注意它的使用场景和性能影响。反射操作通常比直接调用方法和访问字段的性能要低，因此在对性能要求较高的场景应谨慎使用。</p>
<h2 id="java创建对象有几种方式" tabindex="-1"><a class="header-anchor" href="#java创建对象有几种方式"><span>Java创建对象有几种方式</span></a></h2>
<ol>
<li><strong>使用new关键字</strong></li>
<li><strong>使用反射</strong></li>
<li><strong>使用newInstance()方法</strong></li>
<li><strong>使用clone()方法</strong></li>
<li><strong>使用对象的反序列化</strong></li>
</ol>
<h2 id="如果实现线程的同步" tabindex="-1"><a class="header-anchor" href="#如果实现线程的同步"><span>如果实现线程的同步</span></a></h2>
<ol>
<li><strong>使用synchronized关键字：</strong> 通过在方法或代码块前加synchronized关键字，确保同一时间只有一个线程可以执行标记为同步的代码。这样可以避免多个线程同时访问共享资源造成的数据不一致问题。</li>
<li><strong>使用ReentrantLock类：</strong> 它是一个可重入锁，通过调用Lock()和unlock()方法获取和释放锁。与synchronized不同，ReentrantLock提供了更灵活的同步控制，例如可实现公平性和试锁等待时间。</li>
<li><strong>使用wait()、notify()、notifyAll()方法：</strong> 这些是Object类的方法，允许线程间进行协作和通信。通过调用wait()方法使线程进入等待状态，然后其他线程可以通过notify()或notifyAll()方法唤醒等待的线程。</li>
<li><strong>使用CountDownLatch和CyclocBarrier：</strong> 它们是并发工具类，用于线程之间的的同步和等待。CountDownLatch可用于等待一组线程完成操作，而CyclicBarrier用于等待一组线程互相达到屏障位置。</li>
</ol>
<p>CountDownLatch与CyclicBarrier都是用于控制并发的工具类，都可以理解成维护的就是一个计数器，但是这两者还是各有不同侧重点的：</p>
<ol>
<li>CountDownLatch一般用于某个线程A等待若干个其他线程执行完任务之后，它才执行；而CyclicBarrier一般用于一组线程互相等待至某个状态，然后这一组线程再同时执行；CountDownLatch强调一个线程等多个线程完成某件事情。CyclicBarrier是多个线程互等，等大家都完成，再携手共进。</li>
<li>调用CountDownLatch的countDown方法后，当前线程并不会阻塞，会继续往下执行；而调用CyclicBarrier的await方法，会阻塞当前线程，直到CyclicBarrier指定的线程全部都到达了指定点的时候，才能继续往下执行；</li>
<li>CountDownLatch方法比较少，操作比较简单，而CyclicBarrier提供的方法更多，比如能够通过getNumberWaiting()，isBroken()这些方法获取当前多个线程的状态，并且CyclicBarrier的构造方法可以传入barrierAction，指定当所有线程都到达时执行的业务功能；</li>
<li>CountDownLatch是不能复用的，而CyclicLatch是可以复用的。</li>
<li>CountDownlatch基于AQS实现，会将构造CountDownLatch的入参传递至statecountDown()就是在利用CAS将state减1，await()实际就是让头节点一直在等待state为0时，释放所有等待的线程。CyclicBarrier则利用ReentrantLock和Condition，自身维护了count和parties变量。每次调用await将count-1，并将线程加入到condition队列上。等到count为0时，则将condition队列的节点移交至AQS队列，并全部释放。</li>
</ol>
<h2 id="什么是守护线程-与普通线程的区别" tabindex="-1"><a class="header-anchor" href="#什么是守护线程-与普通线程的区别"><span>什么是守护线程？与普通线程的区别</span></a></h2>
<p>守护线程是在程序运行时在后台提供一种支持性的线程。与普通线程相比，守护线程有以下几个区别：</p>
<ol>
<li><strong>终止条件：</strong> 当所有用户线程结束时，守护线程会自动停止。换句话说，守护线程不会阻止程序的终止，即使它们还没有执行完任务。</li>
<li><strong>生命周期：</strong> 守护线程的生命周期与主线程或其他用户线程无关。当所有的非守护线程都结束时，JVM将会退出并停止守护线程的执行。</li>
<li><strong>线程优先级：</strong> 守护线程的优先级默认与普通线程一样。优先级较高的守护线程也不能保证在其他线程之前执行。</li>
<li><strong>资源回收：</strong> 守护线程通常被用于执行一些后台任务，例如垃圾回收、日志记录、定时任务等。当只剩下守护线程是，JVM会自动退出并且不会等待守护线程执行完毕。</li>
</ol>
<p>需要注意的是，守护线程与普通线程在编写代码时没有太大的区别。可以通过将线程的setDaemon(true)方法设置为true，将普通线程转换成守护线程。</p>
<h2 id="java中的集合框架爱有哪些核心接口" tabindex="-1"><a class="header-anchor" href="#java中的集合框架爱有哪些核心接口"><span>Java中的集合框架爱有哪些核心接口</span></a></h2>
<ol>
<li><strong>Collection接口</strong></li>
<li><strong>List接口</strong></li>
<li><strong>Set接口</strong></li>
<li><strong>Queue接口</strong></li>
<li><strong>Map接口</strong></li>
</ol>
<h2 id="arraylist和linkedlist有什么区别" tabindex="-1"><a class="header-anchor" href="#arraylist和linkedlist有什么区别"><span>ArrayList和LinkedList有什么区别</span></a></h2>
<ol>
<li><strong>底层数结构：</strong> ArrayList使用数组来存储元素，而LinkedList使用双向链表来存储元素。</li>
<li><strong>随机访问性能：</strong> ArrayList支持高效的随机访问（根据索引获取元素），因为它可以通过下标计算元素在数组中的位置。而LinkedList在随机访问方面性能较差，获取元素需要从头或尾部开始遍历链表找到对应的位置。</li>
<li><strong>插入和删除性能：</strong> ArrayList在尾部添加或删除元素的性能较好，因为不涉及数组的移动。而在中间插入或删除元素时，ArrayList涉及到元素的移动，性能相对较低。LinkedList在任意位置进行插入和删除操作的性能较好，因为只需要调整链表中的指针即可。</li>
<li><strong>内存占用：</strong> ArrayList在每个元素都要存储一个引用和一个额外的数组空间，因此内存占用比较高。而LinkedList由于需要存储前后节点的引用，相对于ArrayList占用的内存要更多。</li>
</ol>
<h2 id="hashmap和hashtable有什么区别" tabindex="-1"><a class="header-anchor" href="#hashmap和hashtable有什么区别"><span>HashMap和Hashtable有什么区别</span></a></h2>
<ol>
<li><strong>线程安全性：</strong> Hashtable是线程安全的，而HashMap是非线程安全的。</li>
<li><strong>null值：</strong> Hashtable不允许键或值为null，否则会抛出NullPointerException异常。而HashMap可以存储键或值为null的元素。</li>
<li><strong>继承和接口实现：</strong> Hashtable继承自Dictionary类，而HashMap继承自AbstractMap类并实现了Map接口。</li>
<li><strong>初始容量和扩容机制：</strong> Hashtable在创建时必须执行容量大小，且默认大小为11。而HashMap在创建时不需要指定容量大小，系统会自动分配初始容量16，并采用2倍扩容机制。</li>
<li><strong>迭代器：</strong> 迭代器Iterator对Hashtable是安全的，但是对HashMap却不是安全的。因为迭代器被设计为工作于一个快照上，如果在迭代过程中其他线程修改了HashMap，则会抛出并发修改异常。</li>
</ol>
<h2 id="什么是java序列化" tabindex="-1"><a class="header-anchor" href="#什么是java序列化"><span>什么是Java序列化</span></a></h2>
<p><strong>Java的序列化是指将Java对象转换成字节流的过程，进而保存到文件中或通过网络传输。</strong>
<strong>反序列化则是将字节流恢复成对象的过程。</strong>
<strong>序列化的主要目的是实现对象的持久化存储和传输。</strong></p>
<h2 id="说说你对内部类的理解" tabindex="-1"><a class="header-anchor" href="#说说你对内部类的理解"><span>说说你对内部类的理解</span></a></h2>
<p>内部类是Java中一种特殊的类，它定义在其他类和方法中，并且可以访问外部类的成员，包括私有成员。
内部类分成如下几种：</p>
<ol>
<li><strong>成员内部类：</strong></li>
<li><strong>静态内部类：</strong></li>
<li><strong>局部内部类：</strong></li>
<li><strong>匿名内部类：</strong></li>
</ol>
<h2 id="说说你对lambda表达式的理解" tabindex="-1"><a class="header-anchor" href="#说说你对lambda表达式的理解"><span>说说你对lambda表达式的理解</span></a></h2>
<p>Lambda表达式是Java 8引入的一种简洁的语法形式，用于表示匿名函数。它可以作为参数传递给方法或函数接口，并且可以在需要函数式编程特性的地方使用。</p>
<p>Lambda表达式具有以下特点：</p>
<ol>
<li>简洁</li>
<li>函数式编程</li>
<li>闭包</li>
<li>方法引用</li>
</ol>
<p>Lambda表达式的应用场景包括：</p>
<ul>
<li>集合操作</li>
<li>并行操作</li>
<li>事件驱动模型</li>
</ul>
<h2 id="说说你对泛型的理解" tabindex="-1"><a class="header-anchor" href="#说说你对泛型的理解"><span>说说你对泛型的理解</span></a></h2>
<h2 id="notify-和notifyall有什么区别" tabindex="-1"><a class="header-anchor" href="#notify-和notifyall有什么区别"><span>notify()和notifyAll有什么区别</span></a></h2>
<h2 id="静态内部类和非静态内部类有什么区别" tabindex="-1"><a class="header-anchor" href="#静态内部类和非静态内部类有什么区别"><span>静态内部类和非静态内部类有什么区别</span></a></h2>
<h2 id="strings与new-string有什么区别" tabindex="-1"><a class="header-anchor" href="#strings与new-string有什么区别"><span>Strings与new String有什么区别</span></a></h2>
<h2 id="反射中-class-forname和classloader的区别" tabindex="-1"><a class="header-anchor" href="#反射中-class-forname和classloader的区别"><span>反射中，Class.forName和ClassLoader的区别</span></a></h2>
<ul>
<li>Class.forName方法有java.lang.Class类调用，负责根据类型加载类，并执行静态初始化。</li>
<li>ClassLoader是抽象类，提供了灵活的类加载机制，可以自定义加载过程，从不同来源加载类文件。</li>
</ul>
<p>一般情况下，推荐使用ClassLoader来加载和使用类，因为它更灵活，并避免执行静态初始化的副作用。Class.forName主要用于特定场景，如加载数据库驱动程序。</p>
<h2 id="jdk动态代理与cglib实现的区别" tabindex="-1"><a class="header-anchor" href="#jdk动态代理与cglib实现的区别"><span>JDK动态代理与CGLIB实现的区别</span></a></h2>
<ul>
<li>JDK动态代理是基于接口的代理技术，要求目标类必须实现一个或多个接口。</li>
<li>CGLIB是基于继承的代理技术，可以代理普通的类，不需要目标类实现接口。</li>
</ul>
<h2 id="深拷贝和浅拷贝的区别" tabindex="-1"><a class="header-anchor" href="#深拷贝和浅拷贝的区别"><span>深拷贝和浅拷贝的区别</span></a></h2>
<h2 id="谈谈自定义注解的场景及实现" tabindex="-1"><a class="header-anchor" href="#谈谈自定义注解的场景及实现"><span>谈谈自定义注解的场景及实现</span></a></h2>
<h2 id="说说你对设计模式的理解" tabindex="-1"><a class="header-anchor" href="#说说你对设计模式的理解"><span>说说你对设计模式的理解</span></a></h2>
<p>设计模式是一套经过验证的、被广泛应用与软件开发中的解决特定问题的重复利用的方案集合。</p>
<h2 id="设计模式是如何分类的" tabindex="-1"><a class="header-anchor" href="#设计模式是如何分类的"><span>设计模式是如何分类的</span></a></h2>
<ol>
<li>行为型模式</li>
<li>创建型模型</li>
<li>结构性模型</li>
</ol>
<h2 id="抽象工厂和工厂方法模式的区别" tabindex="-1"><a class="header-anchor" href="#抽象工厂和工厂方法模式的区别"><span>抽象工厂和工厂方法模式的区别</span></a></h2>
<ul>
<li><strong>抽象工厂模式</strong> 提供一个接口，用于创建一系列相关或相互依赖的对象，而无需执行具体的类。</li>
<li><strong>工厂方法模式</strong> 将对象的创建延迟到子类中进行。它定义了一个用于创建对象的抽奖方法，由子类决定具体实例化哪个类。</li>
</ul>
<h2 id="什么是值传递和引用传递" tabindex="-1"><a class="header-anchor" href="#什么是值传递和引用传递"><span>什么是值传递和引用传递</span></a></h2>
<h2 id="java支持多继承吗-为什么" tabindex="-1"><a class="header-anchor" href="#java支持多继承吗-为什么"><span>Java支持多继承吗？为什么</span></a></h2>
<h2 id="构造器是否可以被重写" tabindex="-1"><a class="header-anchor" href="#构造器是否可以被重写"><span>构造器是否可以被重写</span></a></h2>
<p><strong>在Java中，构造器不能被直接重写。</strong></p>
<h2 id="char型变量能存储一个中文汉字吗" tabindex="-1"><a class="header-anchor" href="#char型变量能存储一个中文汉字吗"><span>char型变量能存储一个中文汉字吗</span></a></h2>
<p>在Java中，cahr类型是用来表示单个字符的数据类型，它采用Unicode编码，可以存储各种字符，包括中文汉字。</p>
<h2 id="如何实现对象克隆" tabindex="-1"><a class="header-anchor" href="#如何实现对象克隆"><span>如何实现对象克隆</span></a></h2>
<h2 id="for-each和常规for循环的效率区别" tabindex="-1"><a class="header-anchor" href="#for-each和常规for循环的效率区别"><span>for-each和常规for循环的效率区别</span></a></h2>
<ol>
<li><strong>执行效率：</strong> 在大多数情况下，常规for循环的执行效率比for-each循环高。这是因为for-each循环需要额外的步骤来获取集合或数据中的元素，而常规for循环直接通过索引访问元素，避免了额外的开销。</li>
<li><strong>可变性：</strong> 常规的for循环具有更大的灵活性，可以在循环过程中修改计数器，从而控制循环的行为。而for-each循环是只读的，不能在循环过程中修改集合或数据中的元素。</li>
<li><strong>代码简洁性：</strong> for-each循环通常比常规for循环更加简洁易读，尤其在遍历集合或数组时，使用for-each循环可以减少迭代器或索引变量的声明和管理，使代码更加清晰。</li>
</ol>
<h2 id="说说你对懒汉模式和饿汉模式的理解" tabindex="-1"><a class="header-anchor" href="#说说你对懒汉模式和饿汉模式的理解"><span>说说你对懒汉模式和饿汉模式的理解</span></a></h2>
<ul>
<li>
<p><strong>懒汉模式：</strong> 在首次使用时才进行对象的初始化，延迟加载实例。它可以避免不必要的资源消耗，但在多线程环境下需要考虑线程安全和同步开销。</p>
</li>
<li>
<p><strong>饿汉模式：</strong> 在类加载时就进行对象的初始化，无论是否需要。它通过类加载机制保证线程安全性，而且获取实例的性能开销较小。但它没有延迟加载的特性，可能浪费一些资源。</p>
</li>
</ul>
<h2 id="有哪些常见的运行时异常" tabindex="-1"><a class="header-anchor" href="#有哪些常见的运行时异常"><span>有哪些常见的运行时异常</span></a></h2>
<p>运行时异常是在Java程序运行过程中才会出现的异常，通常情况下不需要进行try-catch处理。以下是5个常见的运行时异常：</p>
<ol>
<li>空指针异常。</li>
<li>数组越界异常。</li>
<li>类转换异常。</li>
<li>非法参数异常。</li>
<li>非法状态异常。</li>
</ol>
<h2 id="_2个不相等的对象有可能具有相同的hashcode吗" tabindex="-1"><a class="header-anchor" href="#_2个不相等的对象有可能具有相同的hashcode吗"><span>2个不相等的对象有可能具有相同的HashCode吗</span></a></h2>
<p><strong>有可能，哈希冲突</strong></p>
<h2 id="synchronized的视线原理" tabindex="-1"><a class="header-anchor" href="#synchronized的视线原理"><span>synchronized的视线原理</span></a></h2>
<p><strong>synchronized是通过互斥锁来控制线程对共享变量的访问。</strong>
具体实现原理如下：</p>
<ol>
<li>synchronized的实现基础是对象内部的锁（也称为监视器锁或管程），每个锁关联着一个对象实例。</li>
<li>当synchronized作用于某个对象是，它就会尝试去获取这个对象的锁，如果锁没有被其他线程占用，则当前线程获取到锁，并可以执行同步代码块；如果锁以及被其他线程占用，那么当前线程就会阻塞在同步块之外，直到获取锁才能进入同步块。</li>
<li>synchronized还支持作用于雷伤，此时它锁住的是整个类，而不是类的某个实例。在这种情况下，由于只有一个锁存在，所以所有使用该类的线程都需要等待锁的释放。</li>
<li>在JVM内部，每个Java对象都有头信息，其中包含了对象的一些元信息和状态标志。<strong>synchronized通过修改头信息的标志来实现锁的获取和释放</strong>。</li>
<li>synchronized还支持可重入性，即在同一个线程中可以多次获取同一个锁，这样可以避免死锁问题。</li>
<li>Java虚拟机会通过锁升级的方式来提升synchronized的效率，比如偏向锁、轻量级锁和重复级锁等机制，使得在竞争不激烈的情况下，synchronized的性能可以达到与非同步代码相当的水平。</li>
</ol>
<h2 id="synchronized的锁优化" tabindex="-1"><a class="header-anchor" href="#synchronized的锁优化"><span>synchronized的锁优化</span></a></h2>
<p>在Java6及以上版本中，JVM引入了偏向锁、轻量级锁和重量级锁的概念来提高锁的性能。
这些优化方式的原理如下：</p>
<ul>
<li><strong>偏向锁：</strong> 偏向锁是指当一个线程获取到锁之后，会在对象头记录下该线程的表示，下次再进入同步块时，无需进行额外的加锁操作，从而提高性能。</li>
<li><strong>轻量级锁：</strong> 当多个线程对同一个锁进行争夺时，JVM会使用轻量级锁来避免传统的重量级锁带来的性能消耗。它采用自旋的方式，即不放弃CPU的执行时间，尝试快速获取锁，避免线程阻塞和上下文切换的开销。</li>
<li><strong>重量级锁：</strong> 当多个线程对同一个锁进行强烈争夺时，JVM会升级为重量级锁，此时线程会进入阻塞状态，等待锁的释放。这种方式适用于竞争激烈的情况，但会带来较大的性能开销。</li>
</ul>
<p>锁优化技术是为了提高synchronized的并发性能，根据锁的竞争程序和持有时间的长短选择相应的锁状态，使得多个线程能够更高效地共享资源。</p>
<h2 id="谈谈你对threadlocal的理解" tabindex="-1"><a class="header-anchor" href="#谈谈你对threadlocal的理解"><span>谈谈你对ThreadLocal的理解</span></a></h2>
<h2 id="threadlocal有哪些应用场景" tabindex="-1"><a class="header-anchor" href="#threadlocal有哪些应用场景"><span>ThreadLocal有哪些应用场景</span></a></h2>
<p>在线程池中
在Web开发中
在数据库连接管理中
在日志中
此外，ThreadLocal还可以用于线程之间传递全局的上下文信息。</p>
<h2 id="说说你对countdownlatch的理解" tabindex="-1"><a class="header-anchor" href="#说说你对countdownlatch的理解"><span>说说你对CountDownLatch的理解</span></a></h2>
<p>CountDownLatch是Java中用于多线程协作的辅助类，它可以让一个或多个线程等待其他线程完成某个任务后再继续执行。
CountDownLatch通过一个计数器来实现，计数器的初始化可以设置为等待的线程数量。每个线程在完成任务后都会调用countDown()方法来减少计数器的值。当计数器的值减至0时，等待在CountDownLatch上的线程就会被唤醒，可以继续执行后续的操作。</p>
<p>CountDownLatch的主要作用是协调多个线程的执行顺序，使得某个线程（或多个线程）必须等待其他线程完成后才能继续执行。它常用与一下场景：</p>
<ol>
<li>主线程等待多个子线程完成任务：主线程可以使用await()方法等待所有子线程完成，然后进行结果的汇总或其他操作。</li>
<li>多个线程等待外部事件的发生：多个线程可以同时等待某个共同的事件发生，比如等待某个资源准备就绪或者等待某个信号的触发。</li>
<li>控制并发任务的同时开始：在某些并发场景中，需要等待所有线程都准备就绪后才能同时开始执行任务，CountDownLatch提供了一种更便捷的方式来实现这一需求。</li>
</ol>
<p>需要注意的是，CountDownLatch的计数器是不能被重置的，也就是说它是一次性的。一旦计数器减至0，它将无法再次使用。如果需要多次使用可重置的计数器，则可以考虑CyclicBarrier。</p>
<h2 id="说说你对cyclicbarrier的理解" tabindex="-1"><a class="header-anchor" href="#说说你对cyclicbarrier的理解"><span>说说你对CyclicBarrier的理解</span></a></h2>
<p>CyclicBarrier是Java的一个多线程协作工作，它可以让多个线程在一个屏障点等待，并在所有线程都达到后一起继续执行。与CountDownLatch不同，CyclicBarrier可以重复使用，并且可以指定屏障点后执行的额外工作。
CyclicBarrier的主要特点有三个：</p>
<ol>
<li>可以重复使用，这就意味着当所有线程都到达屏障点后，屏障会自动重置，可用来处理多次需要等待的任务。</li>
<li>CyclicBarrier可以协调多个线程同时开始执行，这在分阶段任务和并发游戏等场景中非常有用。</li>
<li>CyclicBarrier还提供了可选的动作，在所有线程达到屏障点时执行，可以实现额外的逻辑。</li>
</ol>
<p>需要注意的是，在创建CyclicBarrier时需要执行参数线程的数量。一旦所有参与线程都达到屏障点后，CyclicBarrier解除阻塞，所有线程可以继续执行后续操作。</p>
<h2 id="金额到底用long还是bigdecimal" tabindex="-1"><a class="header-anchor" href="#金额到底用long还是bigdecimal"><span>金额到底用Long还是BigDecimal</span></a></h2>
<p><strong>建议：代码层面用BigDecimal，数据库层面视情况而定</strong>
首先long通过在计算机硬件上的性能更好，所以它们的操作在硬件层面上可以更加有效地执行。
而BigDecimal需要额外的空间和计算开销。</p>
<p>阿里额度Java开发是推荐用<code v-pre>分</code>存储的，希望大家都能用Long存储分。<code v-pre>原句：【强制】任何货币金额，均已最小货币单位且为整型类型进行存储</code>。</p>
<p><strong>数据库</strong> 在需求阶段能确定小数点位数可以用long，如果位数不确定，或者要求太精准可以用DECIMAL类型。</p>
<h2 id="日期格式化用yyyy还是yyyy" tabindex="-1"><a class="header-anchor" href="#日期格式化用yyyy还是yyyy"><span>日期格式化用yyyy还是YYYY</span></a></h2>
<p><strong>yyyy</strong>
&quot;2024-12-31 23:59:59&quot;用大写YYYY格式后，变成2025年，因为YYYY表示一个基于周的年份。它根据周计算的年份。
&quot;2024-12-31 23:59:59&quot;用小写yyyy格式后，还是2024年，因为yyyy是基于日历的年份。</p>
</div></template>


